/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

#ifndef __dylab_TetrahedronGeometry_h__
#define __dylab_TetrahedronGeometry_h__

//===========================================================================================
#include <dylab/utils/EnumIterator.h>
#include <dylab/utils/geometry/TriangleGeometry3.h>
#include <dylab/utils/geometry/Vector4.h>
#include <dylab/utils/exceptions/ENumericError.h>
#include <dylab/utils/exceptions/EInvalidArgument.h>

//===========================================================================================
namespace dylab {

    /** 
     */
    class DYLAB_EXPORT TetrahedronGeometry 
    {
    public:
        ///
        enum vertexidx_t
        {
            V_0 = 0,
            V_1,
            V_2,
            V_3
        };
        ///
        class DYLAB_EXPORT VertexIdxIterator : public EnumIterator<vertexidx_t, V_0, V_3> { };
        ///
        enum { V_COUNT = V_3 + 1 };
        ///
        enum edgeidx_t
        {
            E_0 = 0,
            E_1,
            E_2,
            E_3,
            E_4,
            E_5
        };
        ///
        class DYLAB_EXPORT EdgeIdxIterator : public EnumIterator<edgeidx_t, E_0, E_5> { };
        ///
        enum faceidx_t
        {
            F_0 = 0,
            F_1,
            F_2,
            F_3
        };
        ///
        class DYLAB_EXPORT FaceIdxIterator : public EnumIterator<faceidx_t, F_0, F_3> { };
        //-----------------------------------------------------------------------------
        //
        struct DYLAB_EXPORT FeatureIdx
        {
            ///
            union 
            {
                vertexidx_t vertexIdx;
                edgeidx_t   edgeIdx;
                faceidx_t   faceIdx;
            };
            ///
            FeatureIdx()
                    { }
            ///
            FeatureIdx(vertexidx_t _vertexIdx)
                : vertexIdx(_vertexIdx)
                    { }
            ///
            FeatureIdx(edgeidx_t _edgeIdx)
                : edgeIdx(_edgeIdx)
                    { }
            ///
            FeatureIdx(faceidx_t _faceIdx)
                : faceIdx(_faceIdx)
                    { }
        };
        //-----------------------------------------------------------------------------
        class DYLAB_EXPORT pointcls_t 
            : public std::pair<Math::pointcls_t, FeatureIdx>
        { 
        public:
            pointcls_t(Math::pointcls_t pointCls, FeatureIdx featureIdx)
                : std::pair<Math::pointcls_t, FeatureIdx>(pointCls, featureIdx)
                    { }
        };
        //-----------------------------------------------------------------------------

    public:
        /**
        */
        static void getEdgeVertexIndices(edgeidx_t _edgeIdx, 
			vertexidx_t (&_vertexIndices)[EdgeGeometry3::V_COUNT]);

        /** 
         */
        static void getFaceVertexIndices(faceidx_t _faceIdx, 
			vertexidx_t (&_vertexIndices)[TriangleGeometry3::V_COUNT]);

    private:       
        ///
        Vector3 mVertices[V_COUNT];
    public:

        /**          
         */
        TetrahedronGeometry() 
                { }

        /**
        */
        TetrahedronGeometry(const Vector3 & _v0, const Vector3 & _v1, const Vector3 & _v2, const Vector3 & _v3)             
                { mVertices[V_0] = _v0, mVertices[V_1] = _v1, mVertices[V_2] = _v2, mVertices[V_3] = _v3; }

        /**
        */
        TetrahedronGeometry(const Vector3 (&_vertices)[V_COUNT])             
                { for (VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++) mVertices[vertexIdx] = _vertices[vertexIdx]; }

        /**
        */
        TetrahedronGeometry(const TetrahedronGeometry & _tetrahedron) 
                { for (VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++) mVertices[vertexIdx] = _tetrahedron.mVertices[vertexIdx]; }

        /** 
         */
        Vector3 & vertex(vertexidx_t _vertexIdx) 
                { return mVertices[_vertexIdx]; }

        /** 
         */
        const Vector3 & vertex(vertexidx_t _vertexIdx) const
                { return mVertices[_vertexIdx]; }

        /**
        */
        Vector3 & operator[] (vertexidx_t _vertexIdx) 
                { return mVertices[_vertexIdx]; }
        
        /**
        */
        const Vector3 & operator[] (vertexidx_t _vertexIdx) const
                { return mVertices[_vertexIdx]; }

        /**
        */
        Vector3 & operator[] (uint32_t _index) 
                { 
                    DYLAB_ASSERT(_index < V_COUNT);
                    return mVertices[_index]; 
                }
        
        /**
        */
        const Vector3 & operator[] (uint32_t _index) const
                { 
                    DYLAB_ASSERT(_index < V_COUNT);
                    return mVertices[_index]; 
                }

        /**
        */
        EdgeGeometry3 getEdgeGeometry(edgeidx_t _edgeIdx) const
                {
                    vertexidx_t edgeVertexIndices[EdgeGeometry3::V_COUNT];
                    getEdgeVertexIndices(_edgeIdx, edgeVertexIndices);
                    return EdgeGeometry3(mVertices[edgeVertexIndices[V_0]], mVertices[edgeVertexIndices[V_1]]);
                }

        /**
        */
        TriangleGeometry3 getFaceGeometry(faceidx_t _faceIdx) const
                {
                    vertexidx_t faceVertexIndices[TriangleGeometry3::V_COUNT];
                    getFaceVertexIndices(_faceIdx, faceVertexIndices);
                    return TriangleGeometry3(mVertices[faceVertexIndices[V_0]], mVertices[faceVertexIndices[V_1]], mVertices[faceVertexIndices[V_2]]);
                }

        /**
        */
        bool isDegenerated() const
                { return Math::almostZero(computeValue(), DYLAB_R(0.0001)); }                

        /** Computes the value of the tetrahedron.
            @remarks The value is given by the determinat of |v1-v0 v2-v0 v3-v0|
         */
        real_t computeValue() const
                { return (mVertices[V_1] - mVertices[V_0]).dot((mVertices[V_2] - mVertices[V_0]).cross(mVertices[V_3] - mVertices[V_0])); }

        /** 
         */
        Vector3 computeCenter() const
                { 
                    Vector3 cp = Vector3::ZERO;
                    for (VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++)
                        cp += vertex(vertexIdx);
                    return cp / V_COUNT; 
                }

        /**
        */
        real_t computeVolume() const
                { return Math::abs(computeValue() / DYLAB_R(6)); }

        /**
        */
        Vector4 computeBarycentricCoords(const Vector3 & _p) const;

        /**
        */
        pointcls_t classifyPoint(const Vector3 & _p) const;
            
        /**
        */
        operator String() const                
				{ return String::fmt("[%1%, %2%, %3%, %4%]", mVertices[V_0], mVertices[V_1], mVertices[V_2], mVertices[V_3]); }
    };

    //===========================================================================================    
    inline std::ostream & operator << (std::ostream & _os, const TetrahedronGeometry & _o)
            { return _os << String(_o); }

    //===========================================================================================    
    DYLAB_EXPORT std::ostream & operator << (std::ostream & _os, const TetrahedronGeometry::pointcls_t & _pointCls);
}
//===========================================================================================
#endif // __dylab_TetrahedronGeometry_h__
